﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
using Logistics.Web.Models.Common;
using Logistics.Web.Models.Order;
using Logistics.Web.Models.Shipment;
using Logistics.Web.Utilities;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Logistics;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Directory;
using Nop.Services.Helpers;
using Nop.Services.Localization;
using Nop.Services.Logging;
using Nop.Services.Logistics;
using Nop.Services.Payments;
using Nop.Services.Shipping;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Security;

namespace Logistics.Web.Controllers
{
    public class ShipmentController : BaseController
    {
        #region Fields

        private readonly ILogisticsService _logisticsService;
        private readonly ILogisticsProcessingService _logisticsProcessingService;
        private readonly IShipmentService _shipmentSerivce;
        private readonly IWorkContext _workContext;
        private readonly IDateTimeHelper _dateTimeHelper;
        private readonly ILocalizationService _localizationService;
        private readonly IOrderProcessingService _orderProcessingService;
        private readonly LogisticsInformationSettings _logisticsInformationSettings;
        private readonly IShippingService _shippingService;
        private readonly IHolidayService _holidayService;
        private readonly HttpContextBase _httpContext;
        private readonly ILogger _logger;
        private readonly AddressSettings _addressSettings;
        private readonly ICountryService _countryService;
        private readonly IStateProvinceService _stateProvinceService;
        private readonly ICustomerService _customerService;
        private readonly IAddressService _addressService;
        private readonly Nop.Services.Orders.IOrderService _orderService;
        private readonly Nop.Services.Orders.IInvoiceService _invoiceService;
        #endregion

        #region Ctor

        public ShipmentController(
            IShipmentService shipmentService,
            ILogisticsService logisticsService,
            ILogisticsProcessingService logisticsProcessingService,
            IOrderProcessingService orderProcessingService,
            LogisticsInformationSettings logisticsInformationSettings,
            IShippingService shippingService,
            ILocalizationService localizationService,
            IWorkContext workContext,
            HttpContextBase httpContext,
            AddressSettings addressSettings,
            ICountryService countryService,
            IStateProvinceService stateProvinceService,
            IDateTimeHelper dateTimeHelper,
            ILogger logger,
            IHolidayService holidayService,
            ICustomerService customerService,
            IAddressService addressService,
            Nop.Services.Orders.IOrderService orderService,
            Nop.Services.Orders.IInvoiceService invoiceService)
        {
            this._shipmentSerivce = shipmentService;
            this._logisticsService = logisticsService;
            this._logisticsProcessingService = logisticsProcessingService;
            this._orderProcessingService = orderProcessingService;
            this._logisticsInformationSettings = logisticsInformationSettings;
            this._shippingService = shippingService;
            this._localizationService = localizationService;
            this._workContext = workContext;
            this._httpContext = httpContext;
            this._addressSettings = addressSettings;
            this._countryService = countryService;
            this._stateProvinceService = stateProvinceService;
            this._dateTimeHelper = dateTimeHelper;
            this._logger = logger;
            this._holidayService = holidayService;
            this._customerService = customerService;
            this._addressService = addressService;
            this._orderService = orderService;
            this._invoiceService = invoiceService;
        }

        #endregion

        #region Utilities
        [NonAction]
        protected bool IsResidential(AddressType deliveryAddrType, bool isSignatureRequired)
        {
            return (deliveryAddrType == AddressType.Residential && isSignatureRequired);
        }

        [NonAction]
        protected decimal ShipmentDeliveryCharge(decimal speicalCharge, bool isFree)
        {
            decimal residentialCharge = _logisticsInformationSettings.ResidentialCharge;
            decimal charge = isFree == true ? residentialCharge : (speicalCharge + residentialCharge);

            return charge;
        }

        [NonAction]
        protected bool IsPackageResidentialAndUpdatePackageAttribute(LogisticsPackage package, bool isResidential, bool isSignatureRequired, bool canSetSignatureRequirement)
        {
            var attributes = (AddtionalAttributes)package.Shipment.Attributes;
            bool isNewResidential = false;

            if (isResidential)
            {
                if (!attributes.HasFlag(AddtionalAttributes.Residential))
                {
                    // Add "residential"
                    attributes |= AddtionalAttributes.Residential;
                    package.Shipment.Attributes = (int)attributes;
                    _logisticsService.UpdatePackage(package);
                    isNewResidential = true;
                }
            }
            else
            {
                if (attributes.HasFlag(AddtionalAttributes.Residential))
                {
                    // Remove "residential"
                    attributes &= ~AddtionalAttributes.Residential;
                    package.Shipment.Attributes = (int)attributes;
                    _logisticsService.UpdatePackage(package);
                }
            }
            //business addr type must have signature
            if (package.DeliveryLocation.Address.Type == AddressType.Business)
            {
                isSignatureRequired = true;
            }

            if (canSetSignatureRequirement)
            {
                if (isSignatureRequired)
                {
                    if (attributes.HasFlag(AddtionalAttributes.NoSignature))
                    {
                        // Remove "no signature"
                        attributes &= ~AddtionalAttributes.NoSignature;
                        package.Shipment.Attributes = (int)attributes;
                        _logisticsService.UpdatePackage(package);
                    }
                }
                else
                {
                    if (!attributes.HasFlag(AddtionalAttributes.NoSignature))
                    {
                        // Add "no signature"
                        attributes |= AddtionalAttributes.NoSignature;
                        package.Shipment.Attributes = (int)attributes;
                        _logisticsService.UpdatePackage(package);
                    }
                }
            }

            return isNewResidential;
        }

        [NonAction]
        public string RenderPager(int pageIndex, int totalPages, int totalCount, bool hasPrev, bool hasNext)
        {
            var pager = "";
            pageIndex += 1;
            if (totalPages > 1)
            {
                pager += "<ul class='pagination'>";
                pager += "<li class='" + (hasPrev ? "" : "disabled") + " prev'><a href='#' data-page='prev' aria-label='Previous'><span aria-hidden='true'>&laquo;</span></a></li>";
                var pages = new List<int>();
                if (totalPages <= 10)
                {
                    for (var i = 1; i <= 10; i++)
                    {
                        if (i <= totalPages)
                        {
                            pages.Add(i);
                        }
                    }
                }
                else
                {
                    pages.Add(pageIndex);
                    var decMax = 4;
                    for (var i = 1; i < 10; i++)
                    {
                        if (pageIndex - i > 0 && decMax > 0)
                        {
                            pages.Add(pageIndex - i);
                            decMax -= 1;
                        }
                        else
                        {
                            var incDif = 0;
                            if ((pageIndex + i - (4 - decMax)) > totalPages)
                            {
                                pages.Add(pageIndex - i + incDif);
                            }
                            else
                            {
                                pages.Add(pageIndex + i - (4 - decMax));
                                incDif += 1;
                            }
                        }
                    }
                    pages.Sort();
                }
                foreach (var page in pages)
                {
                    pager += "<li class='" + (page == pageIndex ? "active" : "") + " page'><a href='#' data-page='" + page + "'>" + page + "</a></li>";
                }
                pager += "<li class='" + (hasNext ? "" : "disabled") + " next'><a href='#'  data-page='next' aria-label='Next'><span aria-hidden='true'>&raquo;</span></a></li>";
                pager += "</ul>";
            }
            return pager;
        }

        [NonAction]
        protected Address FindAddress(Customer customer, ChangeAddressModel model)
        {
            var address = new Address();
            if (model.SelectedAddressId > 0)
                address = customer.Addresses.ToList().FirstOrDefault(x => x.Id == model.SelectedAddressId);
            else
                address = customer.Addresses.ToList().FindAddress(model.Address.FirstName,
                        model.Address.LastName, model.Address.PhoneNumber, model.Address.CellPhoneNumber, model.Address.Email,
                        model.Address.FaxNumber, model.Address.Company, model.Address.Address1, model.Address.Address2,
                        model.Address.City, model.Address.StateProvinceId, model.Address.ZipPostalCode, model.Address.CountryId);

            if (address == null)
            {
                address = new Address()
                {
                    FirstName = model.Address.FirstName,
                    LastName = model.Address.LastName,
                    PhoneNumber = model.Address.PhoneNumber,
                    CellPhoneNumber = model.Address.CellPhoneNumber,
                    Email = model.Address.Email,
                    FaxNumber = model.Address.FaxNumber,
                    Company = model.Address.Company,
                    Address1 = model.Address.Address1,
                    Address2 = model.Address.Address2,
                    City = model.Address.City,
                    StateProvinceId = model.Address.StateProvinceId,
                    ZipPostalCode = model.Address.ZipPostalCode,
                    CountryId = model.Address.CountryId,
                    CreatedOnUtc = DateTime.UtcNow,
                    Type = model.Address.Type.HasValue ? model.Address.Type : AddressType.Unknown
                };
                customer.Addresses.Add(address);
                _customerService.UpdateCustomer(customer);
            }
            else if (model.Address != null && address.Type != model.Address.Type)
            {
                if (!address.IsAddressTypeVerified() && model.Address.Type.HasValue)
                {
                    address.Type = model.Address.Type;
                    _addressService.UpdateAddress(address);
                }
            }
            return address;
        }

        [NonAction]
        protected bool IsInterDistrict(AddressModel address1, AddressModel address2)
        {
            var district1 = _logisticsService.GetDistrictByZipPostalCode(address1.ZipPostalCode);
            var district2 = _logisticsService.GetDistrictByZipPostalCode(address2.ZipPostalCode);

            return (district1 != null && district2 != null && district1.Id != district2.Id);
        }

        [NonAction]
        protected void AddressModelToAddress(AddressModel model, Address address, bool permanentAddress = false)
        {
            address.FirstName = model.FirstName;
            address.LastName = model.LastName;
            address.Address2 = model.Address2;
            address.PhoneNumber = model.PhoneNumber;
            address.CellPhoneNumber = model.CellPhoneNumber;
            address.Type = model.Type;

            if (!permanentAddress)
            {
                address.Email = model.Email;
                address.Company = model.Company;
                if (model.CountryId > 0)
                {
                    address.CountryId = model.CountryId;
                }
                address.StateProvinceId = model.StateProvinceId > 0 ? model.StateProvinceId : address.StateProvinceId;
                address.City = model.City;
                address.Address1 = model.Address1;
                address.ZipPostalCode = model.ZipPostalCode;
                address.FaxNumber = model.FaxNumber;
            }
        }

        [NonAction]
        private Address AddAddress(AddressModel model, Customer customer)
        {
            if (!model.Type.HasValue)
                throw new ArgumentException("AddressModel type must not be null.");

            var address = customer.Addresses.ToList().FindAddress(
                        model.FirstName, model.LastName,
                        model.PhoneNumber, model.CellPhoneNumber,
                        model.Email, model.FaxNumber, model.Company,
                        model.Address1, model.Address2, model.City,
                        model.StateProvinceId, model.ZipPostalCode, model.CountryId);

            if (address == null)
            {
                address = new Address();
                AddressModelToAddress(model, address);
                address.CreatedOnUtc = DateTime.UtcNow;
                customer.Addresses.Add(address);
                _customerService.UpdateCustomer(customer);
            }
            else if (address.Type != model.Type)
            {
                if (address.IsAddressTypeVerified())
                {
                    model.Type = address.Type;
                }
                else
                {
                    address.Type = model.Type;
                    _addressService.UpdateAddress(address);
                }
            }

            return address;
        }

        [NonAction]
        protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment)
        {
            if (shipment == null)
                throw new ArgumentNullException("shipment");

            var model = new ShipmentDetailsModel()
            {
                Id = shipment.Id,
                PackageId = 0,
                TrackingNumber = shipment.TrackingNumber,
                CanCancel = false,
                CanChangeDeliveryAddress = false,
                ShowDetails = false

                // Set all default values here
            };

            //pickup time
            var order = _orderService.GetOrderById(shipment.OrderId);
            if (order != null)
            {
                model.PickUpTimeShow = _logisticsService.GetShowPickUpTime(order.ShipmentDate, order.ShippingTimeWindowsId, order);
                model.PickupTimeUserSelected = order.ShipmentDate.HasValue && order.ShippingTimeWindowsId.HasValue ? order.ShipmentDate.Value.ToString("MM/dd/yyyy") + " " + _logisticsService.GetShippingTimeWindowById(order.ShippingTimeWindowsId.Value).Description : "";
            }

            var package = _logisticsService.GetPackageByShipmentId(shipment.Id);
            if (package != null)
            {
                var customer = _workContext.CurrentCustomer;
                bool isVerified = IsVerified(package, customer, true);
                var packageHandle = _logisticsProcessingService.CreatePackageHandle(package);
                model.PackageId = package.Id;
                model.CanChangeDeliveryAddress = packageHandle.CanChangeDeliveryAddress();
                model.CanScheduleDelivery = packageHandle.CanScheduleDelivery();
                model.CanCancel = packageHandle.CanCancel() && customer.Id == package.ApplicatorId;
                model.CanChangePickupAddress = packageHandle.CanChangePickupAddress() && customer.Id == package.ApplicatorId;
                model.Status = package.Status.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id);
                ViewBag.Comment = package.Shipment.Comment;
                model.CanChangeComment = isVerified && (package.Status == ShippingStatus.Delivering || package.Status == ShippingStatus.FailedPickUp || package.Status == ShippingStatus.InWarehouse ||
                    package.Status == ShippingStatus.InWarehouseFailedDelivery || package.Status == ShippingStatus.ReadyForPickUp || package.Status == ShippingStatus.ReturningWarehouse || package.Status == ShippingStatus.ReturningWarehouseFailedDelivery);
                var attributes = (AddtionalAttributes)package.Shipment.Attributes;

                if (attributes != AddtionalAttributes.None)
                {
                    model.PackageAttributes = ((AddtionalAttributes)package.Shipment.Attributes.Value)
                                .GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id);
                }
                model.PickupAddress = PrepareAddressModel(package.PickUpLocation.Address);
                model.DeliveryAddress = PrepareAddressModel(package.DeliveryLocation.Address);
                if (isVerified)
                {
                    model.ShowDetails = true;
                    // Set all detailed fields and address models
                    
                    model.Id = shipment.Id;
                }
                else
                {
                    if (IsVerified(package, customer, true))
                    {
                        model.PackageId = package.Id;
                    }
                    var addr = package.DeliveryLocation.Address;

                    // prepare verification info
                    //if (!String.IsNullOrWhiteSpace(addr.Email) && addr.Email.Length >= 5)
                    //{
                    //    Regex regex = new Regex(
                    //        @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|"
                    //        + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)"
                    //        + @"@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$", RegexOptions.IgnoreCase);
                    //    if (!regex.IsMatch(addr.Email))
                    //    {
                    //        _logger.Error(String.Format("Cannot generate Email verification info. {0}", addr.Email));
                    //    }
                    //    else
                    //    {
                    //        model.VerifyEmail = addr.Email.Substring(0, 3).PadRight(addr.Email.Length, '*');
                    //    }
                    //}

                    //string phoneNumber = null;
                    //if (!String.IsNullOrWhiteSpace(addr.CellPhoneNumber) && addr.CellPhoneNumber.Length >= 5)
                    //{
                    //    phoneNumber = addr.CellPhoneNumber;
                    //}
                    //else if (!String.IsNullOrWhiteSpace(addr.PhoneNumber) && addr.PhoneNumber.Length >= 5)
                    //{
                    //    phoneNumber = addr.PhoneNumber;
                    //}
                    //if (phoneNumber != null)
                    //{
                    //    const int showNumberLen = 3;
                    //    model.VerifyPhoneNumber = new String(phoneNumber.Select(x => Char.IsNumber(x) ? '#' : x).ToArray(), 0, phoneNumber.Length - showNumberLen)
                    //        + phoneNumber.Substring(phoneNumber.Length - showNumberLen, showNumberLen);
                    //}

                    //if (!String.IsNullOrWhiteSpace(addr.LastName) && !String.IsNullOrWhiteSpace(addr.FirstName))
                    //{
                    //    model.VerifyName = addr.FirstName;
                    //}
                }
            }

            if (shipment.Order != null && !String.IsNullOrEmpty(shipment.TrackingNumber))
            {
                var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(shipment.Order.ShippingRateComputationMethodSystemName);
                if (srcm != null && srcm.ShipmentTracker != null)
                {
                    var shipmentEvents = srcm.ShipmentTracker.GetShipmentEvents(shipment.TrackingNumber);
                    if (shipmentEvents != null && shipmentEvents.Count > 0)
                    {
                        shipmentEvents.OrderByDescending(s => s.DateTime);
                        model.ShipmentStatusEvents = shipmentEvents.Select(e => new ShipmentDetailsModel.ShipmentStatusEventModel()
                        {
                            Date = e.DateTime.Value.ToString(),
                            EventName = e.EventName,
                            Location = e.Location
                        }).ToList();
                    }
                }
            }
            if (package != null)
                model = PrepareDisplayedEvents(model, package.Status);

            //int[] trackingHistoryIds = new int[4];
            //var nextTrackingName = "";
            //long expiryTimestamp = 0;
            //for (var i = 1; i <= 4; i++)
            //{
            //    if (Request.Cookies["pkgtra" + i] != null)
            //    {
            //        var value = Request.Cookies["pkgtra" + i].Value;
            //        Regex regex = new Regex(@"_\d+$");
            //        Match match = regex.Match(value);
            //        if (match.Length > 0)
            //        {
            //            value = value.Replace(match.Value, "");
            //            long timestamp = 0;
            //            var rawTimestamp = match.Value.Replace("_", "");
            //            Int64.TryParse(rawTimestamp, out timestamp);
            //            if (timestamp != 0)
            //            {
            //                if (timestamp < expiryTimestamp || expiryTimestamp == 0)
            //                {
            //                    nextTrackingName = "pkgtra" + i;
            //                    expiryTimestamp = timestamp;
            //                }
            //            }
            //        }
            //        int shipmentHistoryId = 0;
            //        Int32.TryParse(value, out shipmentHistoryId);
            //        if (shipmentHistoryId > 0)
            //            trackingHistoryIds[i - 1] = shipmentHistoryId;

            //    }
            //    else
            //    {
            //        nextTrackingName = "pkgtra" + i;
            //    }
            //}
            //model.NextTrackingName = nextTrackingName;
            //model.RecentlyViewedShipmentHistory = new List<ShipmentDetailsModel.RecentlyViewedShipment>();
            //if (trackingHistoryIds.Length > 0)
            //{
            //    var shipments = _logisticsService.GetPackagesByShipmentIds(trackingHistoryIds);
            //    if (shipments.Count > 0)
            //    {
            //        foreach (var p in shipments)
            //        {
            //            var timeZone = _dateTimeHelper.FindTimeZoneById(_dateTimeHelper.CurrentTimeZone.Id);
            //            var eventType = 1;
            //            if (p.Status == ShippingStatus.Cancelled || p.Status == ShippingStatus.FailedPickUp || p.Status == ShippingStatus.ReturningWarehouseFailedDelivery)
            //                eventType = 3;
            //            if (p.Status == ShippingStatus.Delivered)
            //                eventType = 2;
            //            if (p.Status == ShippingStatus.Cancelled || p.Status == ShippingStatus.InWarehouseFailedDelivery)
            //                eventType = 4;
            //            var updateTime = p.UpdatedOnUtc != null ? p.UpdatedOnUtc.Value : p.CreatedOnUtc;
            //            model.RecentlyViewedShipmentHistory.Add(new ShipmentDetailsModel.RecentlyViewedShipment()
            //            {
            //                TrackingNumber = p.Shipment.TrackingNumber,
            //                ShipmentId = p.ShipmentId,
            //                EventName = p.Status.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id),
            //                LastUpdatedDateTime = TimeZoneInfo.ConvertTimeFromUtc(updateTime, timeZone),
            //                Type = eventType
            //            });
            //        }
            //    }
            //}

            if (!_logisticsInformationSettings.EnableEditOnTracking)
            {
                model.CanCancel = false;
                model.CanChangePickupAddress = false;
                model.CanChangeDeliveryAddress = false;
                model.CanScheduleDelivery = false;
            }
            return model;
        }

        [NonAction]
        protected OrderInformation CreateOrderInfo(LogisticsPackage package)
        {
            var customer = _workContext.CurrentCustomer;
            if (customer.BillingAddress == null)
                return null;

            var orderInfo = customer.GetCompanyNameOrFullName();
            bool isReceiver = customer.Id != package.Shipment.Order.Customer.Id;
            var oi = new OrderInformation()
            {
                UserAddressId = isReceiver ? package.Shipment.Order.Customer.ShippingAddress.Id : customer.ShippingAddress.Id,
                CurrentLocationId = 1
            };

            var li = new LocationInformation()
            {
                RegionId = 1,
                Id = 1,
                PackageNumber = 1,
                PickupDate = null,
                PickupTimeWindowId = null,
                CanDeliverOnSat = false,
                CanDeliverOnSun = false,
                AddressId = customer.BillingAddress.Id,
                IsUserAddressPickup = true,
                OrderInfo = orderInfo
            };

            oi.Locations.Add(li);

            var pi = new PackageInformation()
            {
                PackageId = package.Id,
                AddtionalAttributes = AddtionalAttributes.None,
                IsLettersize = true,
                IsCompactSize = false,
                IsFullSize = false,
                DimensionMeasureId = 1,
                WeightMeasureId = 2
            };

            li.Packages.Add(pi);
            _orderProcessingService.SaveOrderInformation(oi, customer);
            return oi;
        }

        private ShipmentDetailsModel PrepareDisplayedEvents(ShipmentDetailsModel model, ShippingStatus status)
        {
            model.DisplayedEvents = new List<ShipmentDetailsModel.DisplayedShipmentStatusEvent>();
            // 1: In Progress(Default) 2: Completed 3: Failed 4: Halted
            model.DisplayedStatusType = 1;
            switch (status)
            {
                case ShippingStatus.ReadyForPickUp:
                case ShippingStatus.NotYetShipped:
                case ShippingStatus.NotYetReady:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 2
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 0;
                    break;

                case ShippingStatus.ReturningWarehouse:
                case ShippingStatus.Shipped:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 2
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 1;
                    break;
                case ShippingStatus.FailedPickUp:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 2
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 0;
                    model.DisplayedStatusType = 3;
                    break;
                case ShippingStatus.InWarehouse:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 2
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 2;
                    break;
                case ShippingStatus.Delivering:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 2
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 3;
                    break;
                case ShippingStatus.Delivered:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 2
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 4;
                    model.DisplayedStatusType = 2;
                    break;
                case ShippingStatus.Cancelled:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Cancelled",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 1;
                    model.DisplayedStatusType = 3;
                    break;
                case ShippingStatus.ReturningWarehouseFailedDelivery:
                case ShippingStatus.InWarehouseFailedDelivery:
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Shipped",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "In Transit",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivery Attempt Failed",
                        BarWidth = 1
                    });
                    model.DisplayedEvents.Add(new ShipmentDetailsModel.DisplayedShipmentStatusEvent()
                    {
                        EventName = "Delivered",
                        BarWidth = 1
                    });
                    model.DisplayedEventsBarLocation = 3;
                    model.DisplayedStatusType = 4;
                    break;
            }
            return model;
        }

        [NonAction]
        protected AddressModel PrepareAddressModel(Address address)
        {
            var model = new AddressModel();
            if (address == null)
            {
                address = new Address();
            }
            else
            {
                model.Id = address.Id;
            }

            model.CompanyEnabled = _addressSettings.CompanyEnabled;
            model.CompanyRequired = _addressSettings.CompanyRequired;
            model.CountryEnabled = _addressSettings.CountryEnabled;
            model.StateProvinceEnabled = _addressSettings.StateProvinceEnabled;
            model.CityEnabled = _addressSettings.CityEnabled;
            model.CityRequired = _addressSettings.CityRequired;
            model.StreetAddressEnabled = _addressSettings.StreetAddressEnabled;
            model.StreetAddressRequired = _addressSettings.StreetAddressRequired;
            model.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled;
            model.StreetAddress2Required = _addressSettings.StreetAddress2Required;
            model.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled;
            model.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired;
            model.CellPhoneEnabled = _addressSettings.CellPhoneEnabled;
            model.CellPhoneRequired = _addressSettings.CellPhoneRequired;
            model.PhoneEnabled = _addressSettings.PhoneEnabled;
            model.PhoneRequired = _addressSettings.PhoneRequired;
            model.FaxEnabled = _addressSettings.FaxEnabled;
            model.FaxRequired = _addressSettings.FaxRequired;

            model.Company = address.Company != null ? address.Company : "";
            model.FirstName = address.FirstName != null ? address.FirstName : "";
            model.LastName = address.LastName != null ? address.LastName : "";
            model.Address1 = address.Address1 != null ? address.Address1 : "";
            model.Address2 = address.Address2 != null ? address.Address2 : "";
            model.City = address.City != null ? address.City : "";

            //countries
            model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" });
            foreach (var c in _countryService.GetAllCountries(true))
                model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == address.CountryId) });
            //states
            if (address.Country == null)
            {
                //Get all published provinces in Canada
                //TODO: This is for Canada only at this point.
                var c = _countryService.GetCountryByTwoLetterIsoCode("CA");
                address.CountryId = c.Id;
            }
            var states = address.CountryId != null ? _stateProvinceService.GetStateProvincesByCountryId(address.CountryId.Value, true).ToList() : new List<StateProvince>();
            if (states.Count > 0)
            {
                foreach (var s in states)
                    model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) });
            }
            else
                model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" });




            if (address.Type.HasValue && address.Type.Value != AddressType.Unknown)
            {
                model.Type = address.Type.Value;
            }

            //TODO: remove hard code for province
            if (address.StateProvince != null)
            {
                model.StateProvinceId = address.StateProvinceId;
                model.StateProvinceName = address.StateProvince.GetLocalized(x => x.Name);
            }

            model.ZipPostalCode = address.ZipPostalCode != null ? address.ZipPostalCode : "";

            //TODO: remove hard code for country
            model.CountryName = address.Country == null ? AddressHelper.GetDefaultCountryName() : address.Country.Name;
            model.CountryId = address.CountryId != null ? address.CountryId : 0;
            model.Email = address.Email != null ? address.Email : "";
            model.CellPhoneNumber = address.CellPhoneNumber != null ? address.CellPhoneNumber : "";
            model.PhoneNumber = address.PhoneNumber != null ? address.PhoneNumber : "";
            return model;
        }

        [NonAction]
        protected void PrepareChangeAddressModel(ChangeAddressModel model, LogisticsPackage package, ChangeAddressType type)
        {
            var customer = _workContext.CurrentCustomer;
            if (model == null)
                model = new ChangeAddressModel();
            model.TypeId = (int)type;
            model.PackageId = package.Id;
            model.ShipmentId = package.ShipmentId;
            model.CanSetSignatureRequirement = (type == ChangeAddressType.Delivery);
            model.IsSignatureRequired = !(((AddtionalAttributes)package.Shipment.Attributes).HasFlag(AddtionalAttributes.NoSignature));
            model.IsAddressTypeVerified = false;

            if (model.Address == null)
            {
                if (type == ChangeAddressType.Pickup)
                {
                    model.Address = PrepareAddressModel(package.PickUpLocation.Address);
                    model.IsAddressTypeVerified = package.PickUpLocation.Address.IsAddressTypeVerified();
                }
                else if (type == ChangeAddressType.Delivery)
                {
                    model.Address = PrepareAddressModel(package.DeliveryLocation.Address);
                    model.IsAddressTypeVerified = package.DeliveryLocation.Address.IsAddressTypeVerified();
                }
            }
            else
            {
                if (type == ChangeAddressType.Delivery)
                {
                    var addr = _addressService.GetAddressById(model.Address.Id);
                    model.IsAddressTypeVerified = addr.IsAddressTypeVerified();
                }
                model.Address.CompanyEnabled = _addressSettings.CompanyEnabled;
                model.Address.CompanyRequired = _addressSettings.CompanyRequired;
                model.Address.CountryEnabled = _addressSettings.CountryEnabled;
                model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled;
                model.Address.CityEnabled = _addressSettings.CityEnabled;
                model.Address.CityRequired = _addressSettings.CityRequired;
                model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled;
                model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired;
                model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled;
                model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required;
                model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled;
                model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired;
                model.Address.CellPhoneEnabled = _addressSettings.CellPhoneEnabled;
                model.Address.CellPhoneRequired = _addressSettings.CellPhoneRequired;
                model.Address.PhoneEnabled = _addressSettings.PhoneEnabled;
                model.Address.PhoneRequired = _addressSettings.PhoneRequired;
                model.Address.FaxEnabled = _addressSettings.FaxEnabled;
                model.Address.FaxRequired = _addressSettings.FaxRequired;
                //countries
                model.Address.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" });
                foreach (var c in _countryService.GetAllCountries(true))
                    model.Address.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) });
                //states
                var states = model.Address.CountryId != null ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId ?? 0, true).ToList() : new List<StateProvince>();

                if (states.Count > 0)
                {
                    foreach (var s in states)
                        model.Address.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) });
                }
                else
                    model.Address.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" });
            }

            model.Address.IsTypeRequired = true;
            //verified delviery addresstype : business must have signature
            if (model.IsAddressTypeVerified)
            {
                model.AddressTypeStr = String.Format(_localizationService.GetResource("Logistics.Shipment.ChangeAddress.AddressTypeStr"), model.Address.Type.Value);
                if (type == ChangeAddressType.Delivery && model.Address.Type == AddressType.Business)
                {
                    model.CanSetSignatureRequirement = false;
                }
            }

            if (model.Addresses == null || model.Addresses.Count < 1)
            {
                model.Addresses = new List<SelectListItem>();

                if (customer.IsRegistered())
                {
                    model.SelectedAddressId = model.Address.Id;
                }
                else
                {
                    model.Addresses = null;
                }
            }
            PrepareChangeAddressModelConst(customer, model);

        }

        [NonAction]
        protected void PrepareChangeAddressModelConst(Customer customer, ChangeAddressModel model)
        {
            var country = _countryService.GetCountryByTwoLetterIsoCode("CA");
            var stateprovs = _stateProvinceService.GetStateProvincesByCountryId(country.Id).ToList();
            if (stateprovs.Count > 0)
            {
                model.AvailableStateProvinces = stateprovs
                    .Select(x => new SelectListItem() { Value = x.Id.ToString(), Text = x.Name, Selected = false })
                    .ToList();
                model.AvailableStateProvinces.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Logistics.Common.SelectProvince"), Value = "0", Selected = true });
            }
            if (customer.IsRegistered())
            {
                model.Addresses = customer.Addresses.Select(a =>
                    new SelectListItem()
                    {
                        Selected = model.Address != null && model.Address.Id == a.Id,
                        Text = String.IsNullOrEmpty(a.Company) ?
                            String.Format("{0} {1} <br />{2}", a.FirstName, a.LastName, a.FormattedAddress()) :
                            String.Format("{0} {1} ({2})<br />{3}", a.FirstName, a.LastName, a.Company, a.FormattedAddress()),

                        Value = a.Id.ToString()
                    }).ToList();
            }
        }

        [NonAction]
        protected ShipmentVerifyModel PrepareShipmentVerifyModel(ShipmentVerifyModel model = null, LogisticsPackage package = null)
        {
            if (model == null)
                model = new ShipmentVerifyModel();

            if (package != null)
            {
                var addr = package.DeliveryLocation.Address;
                if (!String.IsNullOrWhiteSpace(addr.Email) && addr.Email.Length >= 5)
                {
                    model.VerifyEmail = addr.Email.Substring(0, 3).PadRight(addr.Email.Length, '*');
                }

                string phoneNumber = null;
                if (!String.IsNullOrWhiteSpace(addr.CellPhoneNumber) && addr.CellPhoneNumber.Length >= 5)
                {
                    phoneNumber = addr.CellPhoneNumber;
                }
                else if (!String.IsNullOrWhiteSpace(addr.PhoneNumber) && addr.PhoneNumber.Length >= 5)
                {
                    phoneNumber = addr.PhoneNumber;
                }
                if (phoneNumber != null)
                {
                    const int showNumberLen = 3;
                    model.VerifyPhoneNumber = new String(phoneNumber.Select(x => Char.IsNumber(x) ? '#' : x).ToArray(), 0, phoneNumber.Length - showNumberLen)
                        + phoneNumber.Substring(phoneNumber.Length - showNumberLen, showNumberLen);
                }

                if (!String.IsNullOrWhiteSpace(addr.LastName) && !String.IsNullOrWhiteSpace(addr.FirstName))
                {
                    model.VerifyName = addr.FirstName;
                }
                model.ShipmentId = package.ShipmentId;
            }
            return model;
        }

        [NonAction]
        protected Logistics.Web.Models.Shipment.PaymentInfoModel PreparePaymentInfoModel(string returnUrl, PaymentInfoType type, LogisticsPackage package, int? addressId = null, DateTime? scheduleDate = null, decimal? charge = null)
        {
            var model = new Logistics.Web.Models.Shipment.PaymentInfoModel()
            {
                ReturnUrl = returnUrl,
                PaymentInfoTypeId = (int)type,
            };

            var handle = _logisticsProcessingService.CreatePackageHandle(package);
            if (type == PaymentInfoType.ChangeDeliveryAddress)
            {
                model.AddressId = addressId.Value;
                model.Message = _localizationService.GetResource("Logistics.Tracking.PaymentInfo.ChangeDeliveryAddress");
            }
            else if (type == PaymentInfoType.ScheduleDelivery)
            {
                model.Date = scheduleDate.Value;
                model.Message = _localizationService.GetResource("Logistics.Tracking.PaymentInfo.ScheduleDelivery");
            }

            var customer = _workContext.CurrentCustomer;
            var newCustomer = package.Shipment.Order.Customer;
            if (customer.BillingAddress != null)
            {
                //create a new order info
                //CreateOrderInfo(package);
            }

            //for receiver, we need his/her billing address
            var address = customer.Id == newCustomer.Id ? customer.ShippingAddress : customer.BillingAddress;
            model.CustomerAddress = PrepareAddressModel(address);

            //change delivery address or residential fee need to pay
            model.FuelSurchargeRate = _logisticsInformationSettings.FuelSurchargeRate;
            model.TaxRate = _logisticsInformationSettings.TaxRate;
            if (!charge.HasValue)
            {
                var paymentInfo = _orderProcessingService.LoadPaymentInformation(_workContext.CurrentCustomer);
                charge = paymentInfo.OrderTotal;
            }
            model.Charge = new Charge();
            model.Charge.SubTotalExclTax = charge.Value;
            model.Charge.FuelSurchargeExclTax = model.Charge.SubTotalExclTax * model.FuelSurchargeRate;
            model.Charge.Tax = (model.Charge.FuelSurchargeExclTax + model.Charge.SubTotalExclTax) * model.TaxRate;
            model.PackageId = package.Id;

            return model;
        }

        [NonAction]
        protected List<DateTime> GetAvaliableDeliveryDates(Customer customer)
        {
            var dates = new List<DateTime>();
            int days = _logisticsInformationSettings.AvaliableDaysToScheduleDelivery;

            var dt = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
            dt = TimeZoneInfo.ConvertTime(dt, _dateTimeHelper.GetCustomerTimeZone(customer));
            var date = dt.Date;

            while (days > 0)
            {
                date = date.AddDays(1);
                if (date.DayOfWeek != DayOfWeek.Saturday &&
                    date.DayOfWeek != DayOfWeek.Sunday &&
                    !_holidayService.IsHoliday(date))
                {
                    days--;
                    dates.Add(date);
                }
            }

            return dates;
        }

        [NonAction]
        protected bool IsVerified(LogisticsPackage package, Customer customer = null, bool verifiedReceiver = false)
        {
            var shipment = package.Shipment;
            if (shipment.Order == null)
                return false;

            if (customer == null)
                customer = _workContext.CurrentCustomer;

            if (customer.Id == shipment.Order.CustomerId)
                return true;
            // You are not authorized to view shipping instruction(s) for this package. The shopper should be able to see.
            if (shipment.Order.BoundOrderId.HasValue)
            {
                if (_orderService.GetOrderById(shipment.Order.BoundOrderId.Value).CustomerId == customer.Id)
                    return true;
            }

            // TODO: Move to service? only validate receiver
            if (verifiedReceiver)
            {
                string verificationCookieName = String.Format("spotshub.trackauth.{0}", shipment.Id.ToString() + package.Id.ToString());
                var verificationCookie = _httpContext.Request.Cookies[verificationCookieName];

                if (verificationCookie != null && !String.IsNullOrEmpty(verificationCookie.Value))
                {
                    Guid orderGuid;
                    string value = verificationCookie.Value.Substring(0, 36);

                    if (Guid.TryParse(value, out orderGuid))
                    {
                        return orderGuid == shipment.Order.OrderGuid;
                    }
                }
            }

            return false;
        }

        [NonAction]
        protected bool IsPaid(Customer customer, PaymentInfoType type, bool removeInfoIfNotPaid)
        {
            var processPaymentRequest = _orderProcessingService.LoadPaymentInformation(customer);
            if (processPaymentRequest != null)
            {
                //avoid payment info conflict: 1)place order in confirm process (before place order)2) then try to reschedule delivery or change delivery address 
                if (processPaymentRequest.CustomValues != null && processPaymentRequest.CustomValues.Count == 0) 
                {
                    _orderProcessingService.RemovePaymentInformation(customer);
                }
                else if (processPaymentRequest.GetPaymentType() == type && processPaymentRequest.IsPaid())
                {
                    //to see whether customer pay the bill
                    return true;
                }
                else if (removeInfoIfNotPaid)
                {
                    _orderProcessingService.RemovePaymentInformation(customer);
                }
            }
            return false;
        }

        [NonAction]
        protected void SavePaymentInfo(Customer customer, decimal fee, PaymentInfoType type, int shipmentId, Order order = null)
        {
            var processPaymentRequest = new ProcessPaymentRequest() { 
                OrderTotal = order.OrderTotal,
                OrderId = order.Id
            };
            processPaymentRequest.CustomValues.Add("shipmentId", shipmentId);
            processPaymentRequest.SetPaymentType(type);
            _orderProcessingService.SavePaymentInformation(processPaymentRequest, customer);
        }

        [NonAction] 
        protected Order CreateOrLoadModifyShipmentOrder(decimal charge, PaymentInfoType type, LogisticsPackage package, int? addressId = null, DateTime? scheduleDate = null)
        {
            var customer = _workContext.CurrentCustomer;
            var order = new Order();
            var paymentInfo = _orderProcessingService.LoadPaymentInformation(customer);
            
            if (paymentInfo.OrderId > 0)
            {
                //Load existing order
                order = _orderService.GetOrderById(paymentInfo.OrderId);
                // Check order status
                if (order != null && order.OrderKind == OrderKind.ExpressModify && order.PaymentStatus == Nop.Core.Domain.Payments.PaymentStatus.Pending && !order.Deleted)
                {
                    if (paymentInfo.CustomValues != null && (int)paymentInfo.CustomValues["shipmentId"] == package.ShipmentId && paymentInfo.GetPaymentType() == type)
                    {
                        return order;
                    }
                    _orderService.DeleteOrder(order);
                }
            }

            CreateOrderInfo(package);
            var oi = _orderProcessingService.LoadOrderInformation(customer);
            var paymentInfoModel = PreparePaymentInfoModel("", type, package, addressId, scheduleDate, charge);
            int orderId = _orderProcessingService.PlaceOrderWithoutShipment(oi, customer, new Charge(), package.Id, package.Shipment.OrderId, false, true);
            order = _orderService.GetOrderById(orderId);
            return order;
        }

        [NonAction]
        protected ActionResult ChangeAddress(Customer customer, LogisticsPackage package, ChangeAddressType type, int addressId)
        {
            var handle = _logisticsProcessingService.CreatePackageHandle(package);
            var changeAddressFee = _logisticsInformationSettings.AdditionalDeliveryCharge;

            if (type == ChangeAddressType.Pickup)
            {
                if (!_logisticsProcessingService.ChangePackagePickupAddress(package, addressId))
                    return FatalErrorView(String.Format("Changing Pickup address has failed {0}.", addressId));
                else
                    return RedirectToAction("Details", new { shipmentId = package.ShipmentId });
            }
            else if (type == ChangeAddressType.Delivery)
            {
                //user need not to pay
                if (handle.IsChangeDeliveryAddressFree() ||
                    changeAddressFee <= 0M ||
                    IsPaid(customer, PaymentInfoType.ChangeDeliveryAddress, true))
                {
                    // Change address
                    if (!_logisticsProcessingService.ChangePackageDeliveryAddress(package, addressId))
                        return FatalErrorView(String.Format("Changing delivery address has failed {0}.", addressId));

                    _orderProcessingService.RemovePaymentInformation(customer);
                    return RedirectToAction("Details", new { shipmentId = package.ShipmentId });
                }
            }

            // Try to re-create the payment info
            return RedirectToAction("ChangeAddress", new { packageId = package.Id, typeId = (int)type });
        }

        public string RenderMessages(List<string> notices = null, List<string> warnings = null)
        {
            var messages = "";
            if (notices != null && notices.Count > 0)
            {
                messages += "<div class='alert alert-success alert-dismissible' role='alert'><button type='button' class='close' data-dismiss='alert' aria-label='Close'><span aria-hidden='true'>&times;</span></button><ul class='notices'>";
                foreach (var n in notices)
                {
                    messages += "<li>" + n + "</li>";
                }
                messages += "</ul></div>";
            }
            if (warnings != null && warnings.Count > 0)
            {
                messages += "<div class='alert alert-warning alert-dismissible' role='alert'><button type='button' class='close' data-dismiss='alert' aria-label='Close'><span aria-hidden='true'>&times;</span></button><ul class='notices'>";
                foreach (var w in warnings)
                {
                    messages += "<li>" + w + "</li>";
                }
                messages += "</ul></div>";
            }
            if (!ModelState.IsValid)
            {
                messages += "<div class='alert alert-danger alert-dismissible' role='alert'><button type='button' class='close' data-dismiss='alert' aria-label='Close'><span aria-hidden='true'>&times;</span></button><ul class='errors'>";
                var errorlist = ModelState.Select(x => x.Value.Errors)
                    .Where(y => y.Count > 0)
                    .ToList();
                foreach (var err in errorlist)
                {
                    foreach (var e in err)
                    {
                        messages += "<li>" + e.ErrorMessage + "</li>";
                    }
                }
                messages += "</ul></div>";
            }
            ModelState.Clear();
            return messages;
        }

        #endregion

        #region Shipment List

        [NopHttpsRequirement(SslRequirement.Yes)]
        public ActionResult Index()
        {
            return RedirectToAction("ShipmentList");
        }

        ////[NopHttpsRequirement(SslRequirement.Yes)]
        //public ActionResult ShipmentList()
        //{
        //    var customer = _workContext.CurrentCustomer;

        //    if (!customer.IsRegistered())
        //    {
        //        return AccessDeniedView();
        //    }
        //    return View();
        //}

        [HttpPost]
        public ActionResult ShipmentList(ShipmentListModel model)
        {
            var customer = _workContext.CurrentCustomer;
            var page = model.Page;
            ModelState.Clear();
            if (customer != null)
            {
                var shipments = _logisticsService.SearchTrackingPackages(
                    customer.Id,
                    null,
                    null,
                    "",
                    model.Page - 1,
                    model.PageSize,
                    _logisticsInformationSettings.TrackingIncludeDelivered);
                if (shipments == null)
                {
                    ModelState.AddModelError("", "Unable to find shipments.");
                }
                model = new ShipmentListModel();
                var rows = "";
                if (shipments.Count > 0)
                {
                    foreach (var shipment in shipments)
                    {
                        model.Shipments.Add(shipment.Shipment);
                        rows += "<tr class='shipment-list-row' data-id='" + shipment.Id + "' data-url='" + Url.Action("Details", "Shipment") + "' data-name='ShipmentId' data-value='" + shipment.ShipmentId + "'><td>" + shipment.Shipment.TrackingNumber + "</td><td>" + shipment.CreatedOnUtc.ToShortDateString() + " " + shipment.CreatedOnUtc.ToShortTimeString() + "</td><td>" + shipment.Status + "</td><td>" + "<a href='" + Url.Action("DownloadWaybill", "Order", new { shipmentId = shipment.ShipmentId }) + "' target='_blank' class='btn btn-primary download-waybill'>Download</a></td></tr>";
                    }
                }
                return Json(new
                {
                    data = rows,
                    hiddendata = "<input type='hidden' id='data-page' name='Page' value='" + page + "' />",
                    pagerdata = RenderPager(shipments.PageIndex, shipments.TotalPages, shipments.TotalCount, shipments.HasPreviousPage, shipments.HasNextPage)
                });
            }

            return null;
        }

        #endregion

        #region Tracking

        [HttpPost]
        public ActionResult Details(string TrackingNumber = "", int ShipmentId = 0, bool isOrderDetails = false)
        {
            ModelState.Clear();
            var model = new ShipmentDetailsModel();
            var shipment = new Shipment();
            var warnings = new List<string>();
            var html = "";

            if (!String.IsNullOrEmpty(TrackingNumber))
            {
                var regex = new Regex(_logisticsInformationSettings.TrackingNumberRegex);
                if (!regex.IsMatch(TrackingNumber))
                {
                    ModelState.AddModelError("", "Tracking number pattern is in invalid.");
                }
                else
                {
                    shipment = _logisticsService.GetShipmentByTrackingNumber(TrackingNumber);
                }
            }
            else if (ShipmentId > 0)
            {
                shipment = _shipmentSerivce.GetShipmentById(ShipmentId);
            }
            if (shipment != null && shipment.Id > 0)
            {
                model = PrepareShipmentDetailsModel(shipment);
                html = this.RenderPartialViewToString("Details", model);
                if (!model.ShowDetails) {
                    warnings.Add("You need to verify your ownership to view details about this package.");
                }
            }
            else
            {
                ModelState.AddModelError("", _localizationService.GetResource("Logistics.Tracking.TrackingNumberNotFound"));
            }

            return Json(new
            {
                html = html,
                messages = RenderMessages(null, warnings)
            });
        }

        [HttpPost]
        public ActionResult ShowDetails(int id)
        {
            ModelState.Clear();
            var customer = _workContext.CurrentCustomer;
            var package = _logisticsService.GetPackageByShipmentId(id);
            var notices = new List<string>();
            if (package != null)
            {
                if (IsVerified(package, customer, true))
                {
                    notices.Add("Verification successful. You can view the details of this shipment.");
                    string verificationCookie = String.Format("spotshub.trackauth.{0}", package.ShipmentId.ToString() + package.Id.ToString());
                    HttpCookie cookie = new HttpCookie(verificationCookie);
                    DateTime now = DateTime.Now;

                    // Set the cookie value.
                    cookie.Value = package.Shipment.Order.OrderGuid.ToString();
                    // Set the cookie expiration date.
                    cookie.Expires = now.AddMinutes(60);

                    // Add the cookie.
                    Response.Cookies.Add(cookie);
                    return Json(new
                    {
                        html = "",
                        messages = RenderMessages(notices)
                    });
                }
                else
                {
                    var verifyModel = PrepareShipmentVerifyModel(null, package);
                    verifyModel.RequestType = "ShowDetails";
                    return Json(new
                    {
                        html = this.RenderPartialViewToString("Verification", verifyModel),
                        messages = RenderMessages()
                    });
                }
            }
            ModelState.AddModelError("", "Unable to process your request.");
            return Json(new
            {
                html = "",
                messages = RenderMessages()
            });
        }

        public ActionResult Verification(ShipmentVerifyModel model)
        {
            ModelState.Clear();
            var package = _logisticsService.GetPackageByShipmentId(model.ShipmentId);
            if (package == null)
            {
                _logger.Error(String.Format("GetVerifyToken shipment ID {0}.", model.ShipmentId));
                ModelState.AddModelError("", String.Format("GetVerifyToken shipment ID {0}.", model.ShipmentId));
            }
            else
            {
                // Verify strategy
                int matched = 0;
                var addr = package.DeliveryLocation.Address;
                if (model.VerifyName != null && addr.LastName.Equals(model.VerifyName, StringComparison.InvariantCultureIgnoreCase))
                {
                    matched++;
                }

                if (model.VerifyPhoneNumber != null && ((addr.CellPhoneNumber != null && model.VerifyPhoneNumber.Contains(addr.CellPhoneNumber)) || (addr.PhoneNumber != null && model.VerifyPhoneNumber.Contains(addr.PhoneNumber))))
                {
                    matched++;
                }

                if (model.VerifyEmail != null && model.VerifyEmail.Equals(addr.Email, StringComparison.InvariantCultureIgnoreCase))
                {
                    matched++;
                }

                if (matched >= 2 && package.Shipment != null && package.Shipment.Order != null)
                {
                    string verificationCookie = String.Format("spotshub.trackauth.{0}", package.ShipmentId.ToString() + package.Id.ToString());
                    HttpCookie cookie = new HttpCookie(verificationCookie);
                    DateTime now = DateTime.Now;

                    // Set the cookie value.
                    cookie.Value = package.Shipment.Order.OrderGuid.ToString();
                    // Set the cookie expiration date.
                    cookie.Expires = now.AddMinutes(60);

                    // Add the cookie.
                    Response.Cookies.Add(cookie);
                    if (model.RequestType == "ChangeAddress")
                    {
                        return ChangeAddress(package.Id, model.AddressType);
                    }
                    if (model.RequestType == "ShowDetails")
                    {
                        return ShowDetails(package.ShipmentId);
                    }
                }
            }

            ModelState.AddModelError("", _localizationService.GetResource("Logistics.Tracking.VerifyNotMatch"));

            return Json(new
            {
                messages = RenderMessages(null, null)
            });
        }

        //public ActionResult Tracking(string trackingNumber = "")
        //{
        //    var model = new TrackingModel()
        //    {
        //        TrackingNumber = trackingNumber
        //    };
        //    return PartialView("Tracking", model);
        //}

        [ChildActionOnly]
        public ActionResult TrackForm()
        {
            ViewBag.TrackingNumberRegex = _logisticsInformationSettings.TrackingNumberRegex;
            return PartialView();
        }

        [HttpPost]
        public ActionResult PackageComment(string comment, int packageId)
        {
            string error = null;
            try
            {
                var package = _logisticsService.GetPackageById(packageId);
                if (package != null && !String.IsNullOrEmpty(comment))
                {
                    comment = package.Shipment.Comment + " " + comment;
                    if (comment.Length > _logisticsInformationSettings.MaxCommentLength)
                    {
                        error = String.Format(_localizationService.GetResource("Logistics.Order.Package.Comment.NotExceed"), _logisticsInformationSettings.MaxCommentLength);
                    }
                    else
                    {
                        string message = String.Format(_localizationService.GetResource("Logistics.Order.Package.Comment.Create"), comment);
                        _logisticsService.CreatePackageNote(packageId, PackageNoteType.SystemEvent, message, _workContext.CurrentCustomer.Id, false, null, null);
                        package.Shipment.Comment = comment;
                        _logisticsService.UpdatePackage(package, false);
                    }
                }
            }
            catch (Exception ex)
            {
                error = ex.Message;
            }

            return Json(new
            {
                Error = error
            });
        }

        [HttpPost]
        public ActionResult GetShipmentIdByTrackingNumber(string trackingNumber)
        {
            try
            {
                var shipment = _logisticsService.GetShipmentByTrackingNumber(trackingNumber);
                if (shipment != null)
                {
                    return RedirectToAction("Index", "Home", new { actionName = "Details", controllerName = "Shipment" });
                }
            }
            catch (Exception ex)
            {
                _logger.Error(String.Format("Search tracking number {0}.", trackingNumber), ex);
            }
            return Json(new { Error = _localizationService.GetResource("Logistics.Tracking.TrackingNumberNotFound") });
        }

        [HttpPost]
        public ActionResult ShipmentTrackingForm(string trackingNumber)
        {
            return RedirectToAction("Index", "Home", new { actionName = "Tracking", controllerName = "Shipment", trackingNumber = trackingNumber });
        }

        //[HttpPost]
        //public ActionResult GetVerifyToken(ShipmentDetailsModel model)
        //{
        //    var package = _logisticsService.GetPackageByShipmentId(model.Id);
        //    if (package == null)
        //    {
        //        _logger.Error(String.Format("GetVerifyToken shipment ID {0}.", model.Id));
        //    }
        //    else
        //    {
        //        // Verify strategy
        //        int matched = 0;
        //        var addr = package.DeliveryLocation.Address;
        //        if (model.VerifyName != null && addr.LastName.Equals(model.VerifyName, StringComparison.InvariantCultureIgnoreCase))
        //        {
        //            matched++;
        //        }

        //        if (model.VerifyPhoneNumber != null && (addr.CellPhoneNumber.Contains(model.VerifyPhoneNumber) || model.VerifyPhoneNumber.Contains(addr.CellPhoneNumber)))
        //        {
        //            matched++;
        //        }

        //        if (model.VerifyEmail != null && model.VerifyEmail.Equals(addr.Email, StringComparison.InvariantCultureIgnoreCase))
        //        {
        //            matched++;
        //        }

        //        if (matched >= 2 && package.Shipment != null && package.Shipment.Order != null)
        //        {
        //            return Json(new { Token = package.Shipment.Order.OrderGuid.ToString(), packageId = package.Id });
        //        }
        //    }

        //    return Json(new { Error = _localizationService.GetResource("Logistics.Tracking.VerifyNotMatch") });
        //}

        #endregion

        #region Cancel

        [HttpPost]
        public ActionResult Cancel(CancelShipmentModel model)
        {
            var customer = _workContext.CurrentCustomer;
            if (!customer.IsRegistered())
                ModelState.AddModelError("", "You are not authorized to perform this action.");
            else if (!_logisticsInformationSettings.EnableEditOnTracking)
                ModelState.AddModelError("", "Package editing is disabled.");

            var package = _logisticsService.GetPackageByShipmentId(model.ShipmentId);
            if (package == null || package.Shipment == null || package.Shipment.Order == null)
            {
                ModelState.AddModelError("", String.Format("Shipment #{0} is invalid.", model.ShipmentId));
            }
            else if (package.Shipment.Order.CustomerId != customer.Id)
            {
                ModelState.AddModelError("", String.Format("You are not authorized to perform this action on shipment #{0}.", model.ShipmentId));
            }
            model.TrackingNumber = package.Shipment.TrackingNumber;

            if (!ModelState.IsValid)
            {
                return Json(new
                {
                    html = this.RenderPartialViewToString("CancelShipment", model),
                    messages = RenderMessages(),
                    success = false
                });
            }
            
            try
            {
                if (model.Confirm)
                {
                    if (!_logisticsProcessingService.CancelPackage(package))
                    {
                        ModelState.AddModelError("", _localizationService.GetResource("Logistics.Tracking.CanNotCancel"));
                    }
                    else
                    {
                        model.Success = true;
                        return Json(new
                        {
                            html = this.RenderPartialViewToString("CancelShipment", model),
                            messages = RenderMessages(),
                            success = true
                        });
                    }
                }
                else
                {
                    model.Confirm = true;
                    return Json(new
                    {
                        html = this.RenderPartialViewToString("CancelShipment", model),
                        messages = RenderMessages(),
                        success = true
                    });
                }
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", String.Format("Shipment #{0} cannot be cancelled. Due to {1}", model.ShipmentId, ex.Message));
            }
            return Json(new
            {
                html = this.RenderPartialViewToString("CancelShipment", model),
                messages = RenderMessages(),
                success = false
            });
        }

        #endregion

        #region Change Address

        [HttpPost]
        public ActionResult ChangeAddress(int packageId, int typeId, ChangeAddressModel model = null)
        {
            var customer = _workContext.CurrentCustomer;
            var package = _logisticsService.GetPackageById(packageId);
            if (model == null)
            {
                model = new ChangeAddressModel();
            }
            ModelState.Clear();
            if (package == null || package.Shipment == null || package.Shipment.Order == null)
                ModelState.AddModelError("", String.Format("packageId is invalid {0}.", packageId));

            if (!Enum.IsDefined(typeof(ChangeAddressType), typeId))
                ModelState.AddModelError("", "Address type to be changed is invalid.");

            if (!_logisticsInformationSettings.EnableEditOnTracking)
                ModelState.AddModelError("", "Editing this shipment is not allowed.");
            
            if (!ModelState.IsValid)
            {
                model.Disallowed = true;
                return Json(new
                {
                    html = this.RenderPartialViewToString("ChangeAddress", model),
                    messages = RenderMessages(),
                    success = false
                });
            }

            var addrType = ChangeAddressType.Delivery;
            if (typeId == (int)ChangeAddressType.Pickup)
                addrType = ChangeAddressType.Pickup;
            var verified = IsVerified(package, customer, true);
            var notices = new List<string>();

            if (!verified)
            {
                var verifyModel = PrepareShipmentVerifyModel(null, package);
                verifyModel.RequestType = "ChangeAddress";
                verifyModel.AddressType = typeId;
                return Json(new
                {
                    html = this.RenderPartialViewToString("Verification", verifyModel),
                    success = true
                });
            }

            // Verified. Check if address is submitted
            var handle = _logisticsProcessingService.CreatePackageHandle(package);
            if (model != null && ((model.Address != null && model.Address.Id > 0) || model.SelectedAddressId > 1))
            {
                try
                {
                    // Check if payment is needed (delivery, residential, or more change address requests than allowed for free)
                    var changeAddressFee = _logisticsInformationSettings.AdditionalDeliveryCharge;
                    var address = FindAddress(customer, model);
                    var addressId = address.Id;
                    var targetAddress = PrepareAddressModel(address);
                    if (model.Address == null)
                        model.Address = targetAddress;
                    bool isResidential = IsResidential(model.Address.Type.Value, model.IsSignatureRequired);
                    bool isNewResidential = IsPackageResidentialAndUpdatePackageAttribute(package, isResidential, model.IsSignatureRequired, model.CanSetSignatureRequirement);
                    bool isChangeDeliveryAddressFree = handle.IsChangeDeliveryAddressFree();

                    var compareAddress = new AddressModel();

                    ModelState.Clear();

                    model.Address = targetAddress;
                    ValidateModel(targetAddress);

                    if (!ModelState.IsValid)
                    {
                        return Json(new
                        {
                            html = this.RenderPartialViewToString("ChangeAddress", model),
                            messages = RenderMessages(),
                            success = false
                        });
                    }

                    if (addrType == ChangeAddressType.Delivery)
                        compareAddress = PrepareAddressModel(package.PickUpLocation.Address);
                    else
                        compareAddress = PrepareAddressModel(package.DeliveryLocation.Address);

                    if (IsInterDistrict(compareAddress, targetAddress))
                    {
                        ModelState.AddModelError("", "Shipment between districts is currently not allowed.");
                    }

                    bool isAddressTypeVerified = false;
                    if (!model.Address.Type.HasValue)
                    {
                        //not allow null type address
                        ModelState.AddModelError("", _localizationService.GetResource("Logistics.Order.Fields.Address.Type.Required"));
                    }
                    else
                    {
                        if (address != null && (ChangeAddressType)typeId == ChangeAddressType.Delivery)
                            isAddressTypeVerified = address.IsAddressTypeVerified();
                    }

                    if (!ModelState.IsValid)
                    {
                        return Json(new
                        {
                            html = this.RenderPartialViewToString("ChangeAddress", model),
                            messages = RenderMessages(),
                            success = false
                        });
                    }

                    if (isAddressTypeVerified)
                    {
                        model.IsAddressTypeVerified = true;
                        model.AddressTypeStr = String.Format(_localizationService.GetResource("Logistics.Shipment.ChangeAddress.AddressTypeStr"), model.Address.Type.Value);
                        if (model.Address.Type == AddressType.Business)
                        {
                            model.CanSetSignatureRequirement = false;
                        }
                    }

                    if (typeId == (int)ChangeAddressType.Delivery && (isNewResidential || (!isChangeDeliveryAddressFree && changeAddressFee > 0M && !IsPaid(customer, PaymentInfoType.ChangeDeliveryAddress, false))))
                    {
                        // Payment is required
                        if (model.IsPaymentRequired)
                        {
                            decimal charge = isNewResidential ? ShipmentDeliveryCharge(changeAddressFee, isChangeDeliveryAddressFree) : changeAddressFee;
                            var order = CreateOrLoadModifyShipmentOrder(charge, PaymentInfoType.ChangeDeliveryAddress, package, address.Id);
                            SavePaymentInfo(customer, charge, PaymentInfoType.ChangeDeliveryAddress, package.ShipmentId, order);
                            
                            return PaymentInfo(Url.Action("Details", new { shipmentId = package.ShipmentId }), (int)PaymentInfoType.ChangeDeliveryAddress, package.Id, address.Id);
                        }

                        model.IsPaymentRequired = true;
                        model.SelectedAddressId = address.Id;
                        return Json(new
                        {
                            html = this.RenderPartialViewToString("ChangeAddress", model),
                            messages = RenderMessages(),
                        });
                        
                    }

                    if (ModelState.IsValid)
                    {
                        if (typeId == (int)ChangeAddressType.Pickup)
                        {
                            if (!_logisticsProcessingService.ChangePackagePickupAddress(package, addressId))
                                ModelState.AddModelError("", String.Format("Changing Pickup address has failed {0}.", addressId));

                            if (customer.Id != package.Shipment.Order.CustomerId)
                                ModelState.AddModelError("", "Not autherized for changing pickup address.");

                            if (ModelState.IsValid)
                                notices.Add("Pickup address has been changed successfully.");
                        }
                        else if (typeId == (int)ChangeAddressType.Delivery)
                        {
                            if (!_logisticsProcessingService.ChangePackageDeliveryAddress(package, addressId))
                            {
                                ModelState.AddModelError("", String.Format("Changing delivery address has failed {0}.", addressId));
                            }
                            else
                            {
                                notices.Add("Delivery address has been changed successfully.");
                            }
                            _orderProcessingService.RemovePaymentInformation(customer);
                        }
                    }

                    if (ModelState.IsValid)
                    {
                        model.Success = true;
                        return Json(new
                        {
                            html = this.RenderPartialViewToString("ChangeAddress", model),
                            messages = RenderMessages()
                        });
                    }
                    else
                    {
                        PrepareChangeAddressModelConst(customer, model);
                        return Json(new
                        {
                            html = this.RenderPartialViewToString("ChangeAddress", model),
                            messages = RenderMessages()
                        });
                    }
                }
                catch (Exception ex)
                {
                    PrepareChangeAddressModelConst(customer, model);
                    if (customer.IsAdmin())
                        ModelState.AddModelError("", ex.Message);
                    return Json(new
                    {
                        html = this.RenderPartialViewToString("ChangeAddress", model),
                        messages = RenderMessages()
                    });
                }
            }

            ModelState.Clear();

            // New form
            model = new ChangeAddressModel();
            PrepareChangeAddressModel(model, package, addrType);
            try
            {
                if (typeId == (int)ChangeAddressType.Delivery)
                {
                    if (!verified)
                        ModelState.AddModelError("", "You are not autherized to change the delivery address.");

                    if (!handle.CanChangeDeliveryAddress())
                        ModelState.AddModelError("", "Changing delivery address for this shipment is currently not allowed.");
                }
                else if (typeId == (int)ChangeAddressType.Pickup)
                {
                    if (customer.Id != package.Shipment.Order.CustomerId)
                        ModelState.AddModelError("", "You are not autherized to change the pickup address.");
                }
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", ex.Message);
            }
            if (!ModelState.IsValid)
            {
                var shipmentDetailsModel = PrepareShipmentDetailsModel(package.Shipment);
                return Json(new
                {
                    html = this.RenderPartialViewToString("Details", shipmentDetailsModel),
                    messages = RenderMessages(null, null)
                });
            }
            return Json(new
            {
                html = this.RenderPartialViewToString("ChangeAddress", model),
                messages = RenderMessages(null, null)
            });
        }

        #endregion

        #region Schedule

        [HttpPost]
        public ActionResult ScheduleDelivery(ScheduleDeliveryModel model, bool submitted = false)
        {
            var packageId = model.PackageId;
            var package = _logisticsService.GetPackageById(packageId);
            if (package == null || package.Shipment == null || package.Shipment.Order == null)
                ModelState.AddModelError("", String.Format(_localizationService.GetResource("Logistics.Shipment.ScheduleDelivery.InvalidPackage"), model.PackageId));
            if (!_logisticsInformationSettings.EnableEditOnTracking)
                ModelState.AddModelError("", "EnableEditOnTracking is false.");
            if (!ModelState.IsValid)
            {
                return Json(new
                {
                    html = this.RenderPartialViewToString("ScheduleDelivery", model),
                    messages = RenderMessages()
                });
            }

            var customer = _workContext.CurrentCustomer;
            var handle = _logisticsProcessingService.CreatePackageHandle(package);

            if (!IsVerified(package, customer, true))
            {
                ModelState.AddModelError("", "Not autherized for changing Schedule Delivery.");
                var shipmentDetailsModel = PrepareShipmentDetailsModel(package.Shipment);
                return Json(new
                {
                    html = this.RenderPartialViewToString("Details", shipmentDetailsModel),
                    messages = RenderMessages()
                });
            }
            else
            {
                ModelState.Clear();

                // Check if date submitted
                if (submitted)
                {
                    if (!String.IsNullOrEmpty(model.ScheduledDate))
                    {
                        var changeScheduleDeliveryFee = _logisticsInformationSettings.AdditionalScheduleDeliveryCharge;
                        bool isResidential = IsResidential(package.DeliveryLocation.Address.Type.Value, model.IsSignatureRequired);
                        bool isNewResidential = IsPackageResidentialAndUpdatePackageAttribute(package, isResidential, model.IsSignatureRequired, model.CanSetSignatureRequirement);

                        bool isScheduleDeliveryFree = handle.IsScheduleDeliveryFree();
                        DateTime scheduleDate = DateTime.Parse(model.ScheduledDate);
                        if (isNewResidential || (!isScheduleDeliveryFree && changeScheduleDeliveryFee > 0M && !IsPaid(customer, PaymentInfoType.ScheduleDelivery, false)))
                        {
                            decimal charge = isNewResidential ? ShipmentDeliveryCharge(changeScheduleDeliveryFee, isScheduleDeliveryFree) : changeScheduleDeliveryFee;
                            var order = CreateOrLoadModifyShipmentOrder(charge, PaymentInfoType.ScheduleDelivery, package, null, scheduleDate);
                            SavePaymentInfo(customer, charge, PaymentInfoType.ScheduleDelivery, package.ShipmentId, order);
                            return PaymentInfo(Url.Action("Details", new { shipmentId = package.ShipmentId }), (int)PaymentInfoType.ScheduleDelivery, package.Id, null, scheduleDate);
                        }
                        else
                        {
                            if (isScheduleDeliveryFree || changeScheduleDeliveryFee <= 0M || IsPaid(customer, PaymentInfoType.ScheduleDelivery, true))
                            {
                                if (!_logisticsProcessingService.ScheduleDelivery(package, scheduleDate))
                                    ModelState.AddModelError("", _localizationService.GetResource(("Logistics.Shipment.ScheduleDelivery.Failed"), package.Id));
                                _orderProcessingService.RemovePaymentInformation(customer);
                            }
                        }
                    }
                    else
                    {
                        ModelState.AddModelError("", "Please select a date.");
                    }
                }

                // Show new form
                if (model == null)
                {
                    model = new ScheduleDeliveryModel();
                    model.PackageId = package.Id;
                }
                model.AvaliableDates = GetAvaliableDeliveryDates(package.Shipment.Order.Customer)
                    .Select(x => new SelectListItem() { Text = x.ToShortDateString(), Value = x.ToString("yyyy/MM/dd") })
                    .ToList();

                model.CanSetSignatureRequirement = true;
                model.IsSignatureRequired = !(((AddtionalAttributes)package.Shipment.Attributes).HasFlag(AddtionalAttributes.NoSignature));
                if (package.DeliveryLocation.Address.Type == AddressType.Business)
                {
                    //delivery business address always have signature required
                    model.CanSetSignatureRequirement = false;
                }
            }

            return Json(new
            {
                html = this.RenderPartialViewToString("ScheduleDelivery", model),
                messages = RenderMessages()
            });
        }

        [HttpPost]
        public ActionResult WareHousePickUp(ScheduleDeliveryModel model, bool submitted = false)
        {
            var package = _logisticsService.GetPackageById(model.PackageId);
            if (package == null || package.Shipment == null || package.Shipment.Order == null)
                ModelState.AddModelError("", String.Format("packageId is invalid {0}.", model.PackageId));

            if (!_logisticsInformationSettings.EnableEditOnTracking || !ModelState.IsValid)
            {
                return Json(new
                {
                    redirect = @Url.Action("ShipmentList"),
                    messages = RenderMessages()
                });
            }

            var customer = _workContext.CurrentCustomer;

            var handle = _logisticsProcessingService.CreatePackageHandle(package);

            if (!IsVerified(package, customer, true))
            {
                ModelState.AddModelError("", "Not autherized for warehouse pick up.");
            }
            else
            {
                ModelState.Clear();
                // Check if submitted
                if (submitted)
                {
                    if (ModelState.IsValid)
                    {
                        try
                        {
                            var warehouse = _logisticsService.GetWarehouseById(model.WarehouseId);
                            if (warehouse == null)
                            {
                                ModelState.AddModelError("", "No warehouse found.");
                            }
                            else
                            {
                                if (!_logisticsProcessingService.ScheduleDelivery(package, DateTime.Now, model.WarehouseId))
                                {
                                    ModelState.AddModelError("", "Warehouse pick up failed page.");
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            ModelState.AddModelError("", ex);
                        }
                    }
                }
                else
                {
                    model.PackageId = package.Id;
                    model.AvaliableWarehouses = _logisticsService.GetAllWarehouses(package.DeliveryLocation.District)
                        .Select(x => new SelectListItem() { Text = x.Name, Value = x.Id.ToString() })
                        .ToList();

                    if (model.AvaliableWarehouses == null || model.AvaliableWarehouses.Count == 0)
                    {
                        ModelState.AddModelError("", "No warehouse found.");
                    }

                    return Json(new
                    {
                        html = this.RenderPartialViewToString("WareHousePickUp", model),
                        messages = RenderMessages()
                    });
                }
            }
            var shipmentDetailsModel = PrepareShipmentDetailsModel(package.Shipment);
            return Json(new
            {
                html = this.RenderPartialViewToString("Details", shipmentDetailsModel),
                messages = RenderMessages()
            });
        }
        #endregion

        #region Payment

        public ActionResult PaymentInfo(string returnUrl, int typeId, int packageId, int? addressId = null, DateTime? scheduleDate = null)
        {
            if (!Enum.IsDefined(typeof(PaymentInfoType), typeId))
                ModelState.AddModelError("", "Payment type is invalid.");

            var package = _logisticsService.GetPackageById(packageId);
            if (package == null)
                ModelState.AddModelError("", "PackageId is invalid.");
            if (!_logisticsInformationSettings.EnableEditOnTracking)
                ModelState.AddModelError("", "EnableEditOnTracking is false.");
            if (!ModelState.IsValid)
            {
                return Json(new
                {
                    messages = RenderMessages(),
                    redirect = @Url.Action("ShipmentList"),
                });
            }

            var paymentInfoType = (PaymentInfoType)typeId;
            var model = PreparePaymentInfoModel(returnUrl, paymentInfoType, package, addressId, scheduleDate);

            return Json(new
            {
                html = this.RenderPartialViewToString("PaymentInfo", model),
                messages = RenderMessages()
            });
        }

        [HttpPost, ValidateInput(false)]
        public ActionResult PaymentInfo(FormCollection form, Logistics.Web.Models.Shipment.PaymentInfoModel paymentInfoModel)
        {
            var package = _logisticsService.GetPackageById(paymentInfoModel.PackageId);
            if (package == null)
                ModelState.AddModelError("", "Payment type is invalid.");
            if(!_logisticsInformationSettings.EnableEditOnTracking)
                ModelState.AddModelError("", "EnableEditOnTracking is false.");
            if (!ModelState.IsValid)
            {
                //need error message
                return RedirectToAction("ShipmentList");
            }

            var customer = _workContext.CurrentCustomer;
            var newCustomer = package.Shipment.Order.Customer;
            string returnUrl = form["ReturnUrl"];
            if (String.IsNullOrEmpty(returnUrl))
            {
                ModelState.AddModelError("", "Returl url of payment is invalid.");
            }

            if (customer.Id != newCustomer.Id && customer.BillingAddress == null)
            {
                if (ModelState.IsValid)
                {
                    var addressModel = new ChangeAddressModel();
                    addressModel.Address = paymentInfoModel.CustomerAddress;
                    customer.BillingAddress = FindAddress(customer, addressModel);
                    _customerService.UpdateCustomer(customer);
                }
                var model = PreparePaymentInfoModel(returnUrl, (PaymentInfoType)paymentInfoModel.PaymentInfoTypeId, package, paymentInfoModel.AddressId, paymentInfoModel.Date);
                return Json(new
                {
                    html = this.RenderPartialViewToString("PaymentInfo", model),
                    messages = RenderMessages()
                });
            }

            var processPaymentRequest = _orderProcessingService.LoadPaymentInformation(customer);
            try
            {
                if (processPaymentRequest.TryUpdate(form))
                {
                    processPaymentRequest.SetAsPaid();
                    _orderProcessingService.SavePaymentInformation(processPaymentRequest, customer);

                    if (paymentInfoModel.PackageId > 0)
                    {
                        if (package == null || package.Shipment == null || package.Shipment.Order == null)
                        {
                            //need error message
                            ModelState.AddModelError("", String.Format(_localizationService.GetResource("Logistics.Shipment.ScheduleDelivery.InvalidPackage"), package.Id));
                            return RedirectToAction("ShipmentList");
                        }

                        var handle = _logisticsProcessingService.CreatePackageHandle(package);

                        //place an order 
                        var oi = _orderProcessingService.LoadOrderInformation(customer);
                        oi.OrderCompleted = true;
                        oi.OrderPaid = true;
                        bool isMailingReceiver = (customer.Id != newCustomer.Id && customer.BillingAddress.Email != null);

                        int orderId = _orderProcessingService.PlaceOrderWithoutShipment(oi, customer, paymentInfoModel.Charge, package.Id, package.Shipment.OrderId, isMailingReceiver);
                        //create invoice for paid order without shipment
                        var invoice = _invoiceService.CreateInvoice(orderId, customer, InvoiceStatus.PaidByCreditCard);
                        _orderProcessingService.RemoveCurrentOrder(customer);//Not consider gift card yet

                        if (paymentInfoModel.AddressId > 0)
                        {
                            var address = _addressService.GetAddressById(paymentInfoModel.AddressId);
                            if (address == null)
                                ModelState.AddModelError("", String.Format("Address is invalid: {0}.", paymentInfoModel.AddressId));
                            var changeAddressFee = _logisticsInformationSettings.AdditionalDeliveryCharge;

                            if (ModelState.IsValid && (handle.IsChangeDeliveryAddressFree() || changeAddressFee <= 0M || IsPaid(customer, PaymentInfoType.ChangeDeliveryAddress, true)))
                            {
                                // Change address
                                if (!_logisticsProcessingService.ChangePackageDeliveryAddress(package, paymentInfoModel.AddressId))
                                    ModelState.AddModelError("", String.Format("Changing delivery address has failed {0}.", paymentInfoModel.AddressId));

                                _orderProcessingService.RemovePaymentInformation(customer);

                                if (customer.Id != newCustomer.Id)
                                {
                                    //receiver can download invoice
                                    return RedirectToAction("DownloadInvoice", "Order", new { number = invoice.InvoiceNumber, guid = invoice.InvoiceGuid });
                                }
                            }

                            var shipmentDetailsModel = PrepareShipmentDetailsModel(package.Shipment);
                            return Json(new
                            {
                                html = this.RenderPartialViewToString("Details", shipmentDetailsModel),
                                messages = RenderMessages()
                            });
                        }
                        else if (paymentInfoModel.Date > DateTime.Now)
                        {
                            var changeScheduleDeliveryFee = _logisticsInformationSettings.AdditionalScheduleDeliveryCharge;
                            if (handle.IsScheduleDeliveryFree() || changeScheduleDeliveryFee <= 0M || IsPaid(customer, PaymentInfoType.ScheduleDelivery, true))
                            {
                                if (!_logisticsProcessingService.ScheduleDelivery(package, paymentInfoModel.Date))
                                    ModelState.AddModelError("", String.Format(_localizationService.GetResource("Logistics.Shipment.ScheduleDelivery.Failed"), package.Id));

                                if (ModelState.IsValid)
                                {
                                    _orderProcessingService.RemovePaymentInformation(customer);
                                    if (customer.Id != newCustomer.Id)
                                    {
                                        //receiver can download invoice
                                        return RedirectToAction("DownloadInvoice", "Order", new { number = invoice.InvoiceNumber, guid = invoice.InvoiceGuid });
                                    }
                                }
                            }

                            var shipmentDetailsModel = PrepareShipmentDetailsModel(package.Shipment);
                            return Json(new
                            {
                                html = this.RenderPartialViewToString("Details", shipmentDetailsModel),
                                messages = RenderMessages()
                            });
                        }
                    }
                }
                else
                {
                    int paymentInfoTypeId;
                    if (!int.TryParse(form["PaymentInfoTypeId"], out paymentInfoTypeId)
                        || !Enum.IsDefined(typeof(PaymentInfoType), paymentInfoTypeId))
                    {
                        return FatalErrorView("paymentInfoTypeId of payment is invalid.");
                    }

                    var model = PreparePaymentInfoModel(returnUrl, (PaymentInfoType)paymentInfoTypeId, package);
                    return Json(new
                    {
                        html = this.RenderPartialViewToString("PaymentInfo", model),
                        messages = RenderMessages()
                    });
                }
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", String.Format("Failed to processing the payment due to {0}.", ex));
            }

            return Json(new
            {
                redirect = returnUrl,
                messages = RenderMessages()
            });
        }

        [ChildActionOnly]
        public ActionResult PaymentPaidInfo()
        {
            var model = new PaymentPaidInfoModel() { ShowInfo = false };
            var processPaymentRequest = _orderProcessingService.LoadPaymentInformation(_workContext.CurrentCustomer);
            if (processPaymentRequest != null && processPaymentRequest.IsPaid())
            {
                model.CreditCardName = processPaymentRequest.CreditCardName;
            };
            return View(model);
        }

        #endregion
    }
}